/**
 * Copyright: Copyright (C) Thomas Dixon 2008, Mike Reinhold 2010. All rights reserved.
 * License:   BSD style: $(LICENSE)
 * Authors:   Thomas Dixon, Mike Reinhold, Phil DeMonaco
 */

module tango.util.cipher.Cipher;

private import tango.core.Exception: IllegalArgumentException;

/** Interface for parameters which can be passed to a cipher */
interface CipherParameters {
	string name();
}

/** Interface for parameters which can be passed to a block cipher */
interface BlockCipherParameters : CipherParameters {
}

/** Interface for parameters which can be passed to a stream cipher */
interface StreamCipherParameters : CipherParameters {
}

/** Base symmetric cipher class */
abstract class Cipher {
	static const bool ENCRYPT = true, DECRYPT = false;

	protected bool _initialized, _encrypt;

	/** Returns: The valid key sizes for the cipher */
	uint[] validKeySizes();

	/** Returns: The size of the initialization vector */
	uint ivSize();

	/**
	 * Process a block of plaintext data from the input array
	 * and place it in the output array.
	 *
	 * Params:
	 *     input_  = Array containing input data.
	 *     output_  = Array to hold the output data.
	 *
	 * Returns: The amount of encrypted data processed.
	 */
	uint update(void[] input_, void[] output_);

	/** Returns: The name of the algorithm of this cipher. */
	string name();

	/** Reset cipher to its state immediately subsequent the last init. */
	void reset();

	/**
	 * throw an InvalidArgument exception
	 * 
	 * Params:
	 *     msg = message to associate with the exception
	 */
	static void invalid(char[] msg) {
		throw new IllegalArgumentException(msg);
	}

	/** Returns: Whether or not the cipher has been initialized. */
	final bool initialized() {
		return _initialized;
	}
	
	/** Returns: If the cipher is set for encrypt mode */
	bool encrypt() {
		return _encrypt;
	}

	/**
	 * 
	 * Params:
	 *     encrypt_ = Default Direction Cipher.ENCRYPT or Cipher.DECRYPT
	 *     params_ = Parameter base, SymmetricKey
	 */
	void init(bool encrypt_, SymmetricKey key_);
	
	Cipher dup();
}

/** Interface for a standard block cipher. */
abstract class BlockCipher: Cipher {
	/** Returns: The block size in bytes that this cipher will operate on. */
	uint blockSize();
	
	BlockCipher dup();
	
	int opCmp(Object other){
		return this.name > (cast(BlockCipher)other).name;
	}
}

/** Interface for a standard stream cipher. */
abstract class StreamCipher: Cipher {
	protected {
		ubyte[] _iv;
	}
	
	int opCmp(Object other){
		return this.name > (cast(StreamCipher)other).name;
	}
	
	/**
	 * Initialize the stream cipher with an IV
	 * 
	 * Params:
	 *     iv_ = The initialization vector to use
	 */
	void initIV(IVParameters iv_);
	
	/**
	 * Process one byte of input.
	 *
	 * Params:
	 *     input = Byte to XOR with keystream.
	 *
	 * Returns: One byte of input XORed with the keystream.
	 */
	ubyte returnByte(ubyte input);
	
	StreamCipher dup();
}

/** Interface for the block cipher mode of operation */
abstract class BlockCipherMode: BlockCipher, BlockCipherParameters {

	protected{
		BlockCipher wrappedCipher;
	}
	
	/** 
	 * Initializes the block cipher mode and the wrapped block cipher
	 * 
	 * Params:
	 *     encrypt_ = The initial direction of the cipher
	 *     key_ = The key use for processing input
	 *     ivParams = The initial vector to input into the cipher
	 */
	void init(bool encrypt_, SymmetricKey key_, IVParameters ivParams);

	/** Returns: The name of the cipher and block operation mode */
	string name() {
		return "BlockCipherMode";
	}

	/** Returns: If the mode requires padding */
	bool requiresPadding();
	
	/** Returns: If the cipher is in encrypt mode */
	bool encrypt(){
		return wrappedCipher.encrypt();
	}
	
	/**
	 * Params:
	 *     cipher_ = The block cipher to be wrapped by this cipher mode
	 */
	void cipher(BlockCipher cipher_);

	/** Returns: The block cipher wrapped by the mode object */
	BlockCipher cipher();
	
	BlockCipherMode dup();
}

/** Interface for stream cipher mode of operation */
interface StreamCipherMode: StreamCipherParameters{
	
}

/** Base padding class for implementing block padding schemes. */
abstract class BlockCipherPadding: BlockCipherParameters {
	/** Returns: The name of the padding scheme implemented. */
	string name() {
		return "BlockCipherPadding";
	}

	/** Returns: If the padding allows for safe reordering */
	bool allowsReordering();
	
	/**
	 * Generate padding to a specific length.
	 *
	 * Params:
	 *     len = Length of padding to generate
	 *
	 * Returns: The padding bytes to be added.
	 */
	ubyte[] pad(uint len);

	/**
	 * Return the number of pad bytes in the block.
	 *
	 * Params:
	 *     input_ = Padded block of which to count the pad bytes.
	 *
	 * Returns: The number of pad bytes in the block.
	 *
	 * Throws: dcrypt.crypto.errors.InvalidPaddingError if 
	 *         pad length cannot be discerned.
	 */
	uint unpad(void[] input_);
	
	BlockCipherPadding dup();
}

/** Base class for reordering the input and output blocks for a Cipher */
abstract class BlockCipherOrdering: BlockCipherParameters {

	/** 
	 * Calculate the new block position
	 * 
	 * Params:
	 *     pos = The original position to reorder
	 *     block_size = The block size (or max value) or the reorder function
	 * Returns: The reordered block number
	 */
	uint order(uint pos, uint block_size);

	/** Returns: The name of the Block reordering scheme */
	string name() {
		return "BlockCipherOrdering";
	}
	
	BlockCipherOrdering dup();
}

/** Object representing and wrapping a symmetric key in bytes. */
class SymmetricKey: BlockCipherParameters, StreamCipherParameters {
	private ubyte[] _key;
	
	/**
	 * Params:
	 *     key = Key to be held.
	 */
	this(void[] key = null) {
		_key = cast(ubyte[]) key;
	}
	
	this(SymmetricKey key){
		this._key = key._key.dup;
	}
	
	SymmetricKey dup() {
		return new SymmetricKey(this);
	}

	/** Play nice with D2's idea of const. */
	version(D_Version2) {
		this(string key) {
			this(cast(ubyte[]) key);
		}
	}

	string name() {
		return "Symmetric Key";
	}
	
	/** Returns: Key in ubytes held by this object. */
	ubyte[] key() {
		return _key;
	}

	/**
	 * Set the key held by this object.
	 *
	 * Params:
	 *     newKey = New key to be held.
	 * Returns: The new key.
	 */
	ubyte[] key(void[] newKey) {
		return _key = cast(ubyte[]) newKey;
	}

	/**
	 * Calculate the cipher's preferred key size based on the valid key sizes.
	 * The key is strengthened to the next longest key (when possible). If there is
	 * no longer valid key size, the key will be truncated to the longest valid key length.
	 * 
	 * Params:
	 *     cipher_ = The cipher which will use the key
	 * Returns: The cipher's preferred key size
	 */
	uint setPreferredSize(Cipher cipher_) {
		uint[] sizes = cipher_.validKeySizes().sort;
		uint match = 0;

		//check for an exact key length match
		foreach(uint size; sizes) {
			if(size == _key.length){
				match = size;
				break;
			}
		}

		//if there is no exact key size match, search for the first
		//valid key length which is longer than the provided key material.
		//Extend the key to the key length.
		if(match == 0) {
			foreach(uint size; sizes) {
				if(size > _key.length) {
					match = size;
					break;
				}
			}

			//if there is no longer key length, use the longest
			//valid key length to ensure maximum strength
			if(match == 0) {
				match = sizes[sizes.length - 1];
			}
		}

		//return the length of the key
		return _key.length = match;
	}
}

/** Wrap cipher parameters and IV. */
class IVParameters: BlockCipherParameters, StreamCipherParameters {
	private ubyte[] _iv;

	/**
	 * Params:
	 *     iv     = IV to be held.
	 */
	this(void[] iv = null) {
		_iv = cast(ubyte[]) iv;
	}

	this(IVParameters iv){
		this._iv = iv._iv.dup;
	}
	
	IVParameters dup() {
		return new IVParameters(this);
	}
	
	string name() {
		return "Initialization Vector";
	}
	
	/** Returns: The IV. */
	ubyte[] iv() {
		return _iv;
	}

	/**
	 * Set the IV held by this object.
	 *
	 * Params:
	 *     newIV = The new IV for this parameter object.
	 * Returns: The new IV.
	 */
	ubyte[] iv(void[] newIV) {
		return _iv = cast(ubyte[]) newIV;
	}

	/**
	 * Set the length of the initialization vector
	 * 
	 * Params:
	 *     cipher_ = The cipher that the iv will be passed to
	 * Returns: The new length of the initialization vector
	 */
	uint setIVSize(Cipher cipher_) {
		//Make sure that the IV is at least the block size or the IV size
		if(cast(BlockCipher)cipher_){
			uint block = (cast(BlockCipher)cipher_).blockSize;
			return _iv.length = (cipher_.ivSize > block) ? cipher_.ivSize : block;
		}
		return _iv.length = cipher_.ivSize;
	}
}

/** Struct for performing bitwise operations */
struct Bitwise {
	static uint rotateLeft(uint x, uint y) {
		return (x << y) | (x >> (32u - y));
	}

	static uint rotateRight(uint x, uint y) {
		return (x >> y) | (x << (32u - y));
	}

	static ulong rotateLeft(ulong x, uint y) {
		return (x << y) | (x >> (64u - y));
	}

	static ulong rotateRight(ulong x, uint y) {
		return (x >> y) | (x << (64u - y));
	}
}

/** Converts between integral types and unsigned byte arrays */
struct ByteConverter {
	private static string hexits = "0123456789abcdef";
	private static string base32digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";

	/** Conversions between little endian integrals and bytes */
	struct LittleEndian {
		/**
		 * Converts the supplied array to integral type T
		 * 
		 * Params:
		 *     x_ = The supplied array of bytes (ubytes, bytes, chars, whatever)
		 * 
		 * Returns:
		 *     A integral of type T created with the supplied bytes placed
		 *     in the specified byte order.
		 */
		static T to(T)(void[] x_) {
			ubyte[] x = cast(ubyte[]) x_;

			T result = ((cast(T) x[0]) | ((cast(T) x[1]) << 8));

			static if(T.sizeof >= int.sizeof) {
				result |= ((cast(T) x[2]) << 16) | ((cast(T) x[3]) << 24);
			}

			static if(T.sizeof >= long.sizeof) {
				result |= ((cast(T) x[4]) << 32) | ((cast(T) x[5]) << 40) | ((cast(T) x[6]) << 48) | ((cast(T) x[7]) << 56);
			}

			return result;
		}

		/**
		 * Converts the supplied integral to an array of unsigned bytes.
		 * 
		 * Params:
		 *     input = Integral to convert to bytes
		 * 
		 * Returns:
		 *     Integral input of type T split into its respective bytes
		 *     with the bytes placed in the specified byte order.
		 */
		static ubyte[] from(T)(T input) {
			ubyte[] output = new ubyte[T.sizeof];

			output[0] = cast(ubyte) (input);
			output[1] = cast(ubyte) (input >> 8);

			static if(T.sizeof >= int.sizeof) {
				output[2] = cast(ubyte) (input >> 16);
				output[3] = cast(ubyte) (input >> 24);
			}

			static if(T.sizeof >= long.sizeof) {
				output[4] = cast(ubyte) (input >> 32);
				output[5] = cast(ubyte) (input >> 40);
				output[6] = cast(ubyte) (input >> 48);
				output[7] = cast(ubyte) (input >> 56);
			}

			return output;
		}
	}

	/** Conversions between big endian integrals and bytes */
	struct BigEndian {

		static T to(T)(void[] x_) {
			ubyte[] x = cast(ubyte[]) x_;

			static if(is(T == ushort) || is(T == short)) {
				return cast(T) (((x[0] & 0xff) << 8) | (x[1] & 0xff));
			} else static if(is(T == uint) || is(T == int)) {
				return cast(T) (((x[0] & 0xff) << 24) | ((x[1] & 0xff) << 16) | ((x[2] & 0xff) << 8) | (x[3] & 0xff));
			} else static if(is(T == ulong) || is(T == long)) {
				return cast(T) ((cast(T) (x[0] & 0xff) << 56) | (cast(T) (x[1] & 0xff) << 48) | (cast(T) (x[2] & 0xff) << 40) | (cast(T) (x[3] & 0xff) << 32) | ((x[4] & 0xff) << 24) | ((x[5] & 0xff) << 16) | ((x[6] & 0xff) << 8) | (x[7] & 0xff));
			}
		}

		static ubyte[] from(T)(T input) {
			ubyte[] output = new ubyte[T.sizeof];

			static if(T.sizeof == long.sizeof) {
				output[0] = cast(ubyte) (input >> 56);
				output[1] = cast(ubyte) (input >> 48);
				output[2] = cast(ubyte) (input >> 40);
				output[3] = cast(ubyte) (input >> 32);
				output[4] = cast(ubyte) (input >> 24);
				output[5] = cast(ubyte) (input >> 16);
				output[6] = cast(ubyte) (input >> 8);
				output[7] = cast(ubyte) (input);
			} else static if(T.sizeof == int.sizeof) {
				output[0] = cast(ubyte) (input >> 24);
				output[1] = cast(ubyte) (input >> 16);
				output[2] = cast(ubyte) (input >> 8);
				output[3] = cast(ubyte) (input);
			} else static if(T.sizeof == short.sizeof) {
				output[0] = cast(ubyte) (input >> 8);
				output[1] = cast(ubyte) (input);
			}

			return output;
		}
	}

	static string hexEncode(void[] input_) {
		ubyte[] input = cast(ubyte[]) input_;
		char[] output = new char[input.length << 1];

		int i = 0;
		foreach(ubyte j; input) {
			output[i++] = hexits[j >> 4];
			output[i++] = hexits[j & 0xf];
		}

		return cast(string) output;
	}

	static string base32Encode(void[] input_, bool doPad = true) {
		if(!input_)
			return "";
		ubyte[] input = cast(ubyte[]) input_;
		char[] output;
		auto inputbits = input.length * 8;
		auto inputquantas = inputbits / 40;
		if(inputbits % 40)
			output = new char[(inputquantas + 1) * 8];
		else
			output = new char[inputquantas * 8];

		int i = 0;
		ushort remainder;
		ubyte remainlen;
		foreach(ubyte j; input) {
			remainder = (remainder << 8) | j;
			remainlen += 8;
			while(remainlen > 5) {
				output[i++] = base32digits[(remainder >> (remainlen - 5)) & 0b11111];
				remainlen -= 5;
			}
		}
		if(remainlen)
			output[i++] = base32digits[(remainder << (5 - remainlen)) & 0b11111];
		while(doPad && (i < output.length)) {
			output[i++] = '=';
		}

		return output[0 .. i];
	}

	static ubyte[] hexDecode(string input) {
		string inputAsLower = stringToLower(input);
		ubyte[] output = new ubyte[input.length >> 1];

		static ubyte[char] hexitIndex;
		for(int i = 0; i < hexits.length; i++)
			hexitIndex[hexits[i]] = cast(ubyte) i;

		for(int i = 0, j = 0; i < output.length; i++) {
			output[i] = cast(ubyte) (hexitIndex[inputAsLower[j++]] << 4);
			output[i] |= hexitIndex[inputAsLower[j++]];
		}

		return output;
	}

	static ubyte[] base32Decode(string input) {
		static ubyte[char] b32Index;
		for(int i = 0; i < base32digits.length; i++)
			b32Index[base32digits[i]] = cast(ubyte) i;

		auto outlen = (input.length * 5) / 8;
		ubyte[] output = new ubyte[outlen];

		ushort remainder;
		ubyte remainlen;
		size_t oIndex;
		foreach(c; stringToUpper(input)) {
			if(c == '=')
				continue;
			remainder = (remainder << 5) | b32Index[c];
			remainlen += 5;
			while(remainlen >= 8) {
				output[oIndex++] = cast(ubyte) (remainder >> (remainlen - 8));
				remainlen -= 8;
			}
		}

		return output[0 .. oIndex];
	}

	private static string stringToLower(string input) {
		char[] output = new char[input.length];

		foreach(int i, char c; input)
			output[i] = cast(char) ((c >= 'A' && c <= 'Z') ? c + 32 : c);

		return cast(string) output;
	}

	private static string stringToUpper(string input) {
		char[] output = new char[input.length];

		foreach(int i, char c; input)
			output[i] = cast(char) ((c >= 'a' && c <= 'z') ? c - 32 : c);

		return cast(string) output;
	}
}
